package com.maben.client;

import javafx.util.Pair;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;

import java.io.IOException;
import java.util.*;

public class SnmpV2cClient {
    private Snmp snmp;
    private String targetAddress;
    private String community;
    private CommunityTarget target;

    public SnmpV2cClient(String ipAddress, int port, String community) {
        this.targetAddress = ipAddress + "/" + port;
        this.community = community;
    }

    /**
     * 初始化SNMP连接
     */
    public void initialize() throws IOException {
        TransportMapping<UdpAddress> transport = new DefaultUdpTransportMapping();
        //创建snmp
        snmp = new Snmp(transport);
        //创建target
        target = new CommunityTarget();
        target.setCommunity(new OctetString(community));
        target.setVersion(SnmpConstants.version2c);
        target.setAddress(new UdpAddress(targetAddress));
        target.setRetries(2);
        target.setTimeout(1500);
        //启动监听
        transport.listen();
    }

    /**
     * 关闭连接
     */
    public void close() throws IOException {
        if (snmp != null) {
            snmp.close();
        }
    }

    /**
     * 获取单个OID的值
     */
    public String getOIDValue(String oid) throws IOException {
        PDU pdu = new PDU();
        pdu.add(new VariableBinding(new OID(oid)));
        pdu.setType(PDU.GET);

        ResponseEvent event = snmp.send(pdu, target);
        if (event != null && event.getResponse() != null) {
            VariableBinding vb = event.getResponse().getVariableBindings().firstElement();
            return vb.getVariable().toString();
        }
        return null;
    }

    /**
     * 发送GET BULK请求获取多个OID的值
     */
    public Map<String, String> getBulkValues(List<String> oidList, int maxRepetitions) throws Exception {
        final Map<String, String> result = new HashMap<>();
        PDU pdu = new PDU();
        pdu.setType(PDU.GETBULK);
        pdu.setMaxRepetitions(maxRepetitions);
        oidList.forEach(oid -> pdu.add(new VariableBinding(new OID(oid))));
        ResponseEvent event = snmp.send(pdu, target);
        if (event != null && event.getResponse() != null) {
            PDU response = event.getResponse();
            if (response.getErrorStatus() == PDU.noError) {
                final Vector<? extends VariableBinding> variableBindings = response.getVariableBindings();
                for (VariableBinding variableBinding : variableBindings) {
                    result.put(variableBinding.getOid().toString(), variableBinding.getVariable().toString());
                }
            }
        }
        return result;
    }

    /**
     * 向网络装置设置信息
     */
    public boolean setOIDValue(String oid, Variable value) throws Exception {
        PDU pdu = new PDU();
        pdu.setType(PDU.SET);
        pdu.add(new VariableBinding(new OID(oid), value));
        ResponseEvent event = snmp.send(pdu, target);
        if (event != null) {
            return event.getResponse().getErrorStatus() == PDU.noError;
        }
        return false;
    }

    /**
     *  自动发现指定OID下的所有子OID及其值 (类似snmp walk)
     */
    private Map<String, String> discoverOIDs(String rootOID) throws IOException {
        Map<String, String> oidMap = new LinkedHashMap<>();
        OID currentOID = new OID(rootOID);
        while (true) {
            PDU pdu = new PDU();
            pdu.setType(PDU.GETNEXT); //GETNEXT：获取下一个 OID 的值（用于遍历 MIB 树）
            pdu.add(new VariableBinding(currentOID));
            ResponseEvent event = snmp.send(pdu, target);
            if (event == null || event.getResponse() == null) {
                break;
            }
            VariableBinding vb = event.getResponse().getVariableBindings().firstElement();
            OID responseOID = vb.getOid();
            // 判断是否超出根OID范围
            if (!responseOID.startsWith(new OID(rootOID))) {
                break;
            }
            oidMap.put(responseOID.toString(), vb.getVariable().toString());
            currentOID = responseOID;
        }

        return oidMap;
    }

    /**
     * 获取OID树形结构 (带层级关系)
     */
    public Map<Integer, List<Pair<String, String>>> getOIDTree(String rootOID) throws IOException {
        Map<Integer, List<Pair<String, String>>> tree = new TreeMap<>();
        Map<String, String> oidValues = discoverOIDs(rootOID);

        for (Map.Entry<String, String> entry : oidValues.entrySet()) {
            String oid = entry.getKey();
            int level = new OID(oid).toIntArray().length - new OID(rootOID).toIntArray().length;
            tree.computeIfAbsent(level, k -> new ArrayList<>()).add(new Pair<>(oid, entry.getValue()));
        }

        return tree;
    }


    // 示例用法
    public static void main(String[] args) {
        try {
            // 创建客户端并初始化
            SnmpV2cClient client = new SnmpV2cClient("192.168.10.254", 161, "public");
            client.initialize();
//                                               1.3.6.1.2.1.1.1.0
            String devName = client.getOIDValue("1.3.6.1.2.1.1.5.0");
            System.out.println("设备名称: " + devName);
//            devName = devName.replace(":","");
//            System.out.println("===:"+new String(HexUtil.decodeHex(devName), Charset.forName("GBK")));

            //自动发现指定OID下的所有子OID及其值
//            System.out.println("\n自动发现指定OID下的所有子OID及其值:");
//            final Map<String, String> discoverOIDs = client.discoverOIDs("1.3.6.1.2.1.2");
//            discoverOIDs.forEach((key, value) -> System.out.println(key + ":" + value));

            // 获取OID树形结构
//            System.out.println("\n获取OID树形结构:");
//            final Map<Integer, List<Pair<String, String>>> oidTree = client.getOIDTree("1.3.6.1.2.1.2");
//            oidTree.forEach((key, value) -> System.out.println(key + ":" + JSON.toJSONString(value)));


            // 关闭连接
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}